#include "ota.h"

#include <string.h>
#include "esp_rom_md5.h"
// #include "mbedtls/md5.h"
#include "esp_heap_caps.h"
#include "esp_partition.h"
#include "esp_app_format.h"
#include "esp_flash_partitions.h"
#include "esp_rom_sys.h"
#include "esp_system.h"
#include "nvs.h"

#include "cJSON.h"
#include "file_tool.h"
#ifdef CONFIG_LIBS_USE_DBUS
#include "dbus.h"
#endif

#include "esp_log.h"
static const char *TAG = "OTA";

#ifdef CONFIG_SPIRAM
#define _malloc_(size) heap_caps_malloc(size, MALLOC_CAP_SPIRAM)
#else
#define _malloc_(size) malloc(size)
#endif

#define OTA_MAGIC 0x10090126

ota_t ota_default_handle = 0;

bool ota_overflow_check(ota_t ota)
{
    if (!ota)
    {
        ESP_LOGE(TAG, "!!! OTA Overflow !!!");
        return true;
    }
    if (ota->MAGIC_START != OTA_MAGIC)
    {
        ESP_LOGE(TAG, "!!! OTA stack Overflow !!!");
        return true;
    }
    if (ota->MAGIC_END != OTA_MAGIC)
    {
        ESP_LOGE(TAG, "!!! OTA End Overflow !!!");
        return true;
    }
    return false;
}

ota_plugin_t ota_add_plugin(ota_t ota, ota_plugin_t plugin)
{
    if (!ota || !plugin)
        return NULL;
    ota_plugin_t plugin_ret = _malloc_(sizeof(ota_plugin_s));
    if (!plugin_ret)
        return 0;
    memcpy(plugin_ret, plugin, sizeof(ota_plugin_s));
    plugin_ret->next = NULL;
    if (ota->plugins)
    {
        ota_plugin_t p = ota->plugins;
        while (p->next)
            p = p->next;
        p->next = plugin_ret;
    }
    else
        ota->plugins = plugin_ret;
    return plugin_ret;
}

char *ota_get_progress(ota_t ota)
{
    if (!ota)
        return 0;
    sprintf(ota->progress_str, "%d%%", ota->progress);
    return ota->err_str;
}

uint32_t ota_get_offset(ota_t ota)
{
    if (!ota)
        return 0;
    return ota->offset;
}

void ota_set_par_name(ota_t ota, char *name)
{
    if (!ota || !name)
        return;
    strncpy(ota->par_name, name, sizeof(ota->par_name) - 1);
}

void ota_set_server(ota_t ota, char *server, char *port, char *path, uint8_t type)
{
    if (!ota || !server)
        return;
    memset(ota->url, 0, sizeof(ota->url));
    if (!strcmp(port, "443"))
        strcat(ota->url, "https://");
    else
        strcat(ota->url, "http://");
    strcat(ota->url, server);
    if (strcmp(port, "443") && strcmp(port, "80")) /* port != 443 && port != 80 */
    {
        strcat(ota->url, ":");
        strcat(ota->url, port);
    }
    strcat(ota->url, path);

    ota->type = type & ~OTA_TYPE_FILE;
    if (ota->handle)
        ota->run = 1;
}

#include "http_request.h"
void ota_set_url(ota_t ota, char *url, uint8_t type)
{
    if (!ota || !url)
        return;
    strncpy(ota->url, url, sizeof(ota->url) - 1);
    ota->type = type & ~OTA_TYPE_FILE;
    if (ota->handle)
        ota->run = 1;
}

static void ota_task_handler(void *p)
{
    if (!p)
        return;
    ota_t ota = p;

    ota->MAGIC_START = ota->MAGIC_END = OTA_MAGIC;
    ota->buffer_size = heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT) / 4 * 3;
    if (ota->buffer_size > 32768)
        ota->buffer_size = 32768;
#ifndef CONFIG_SPIRAM
    ota->buffer_size = 8192;
#endif
    while (!ota->buffer)
    {
        ota->buffer = _malloc_(ota->buffer_size);
        if (!ota->buffer)
            ota->buffer_size /= 2;
    }
    ESP_LOGI(TAG, "ota->buffer size(%ld)", ota->buffer_size);

    const esp_partition_t *run_app_partition = esp_ota_get_running_partition();
    if (run_app_partition)
    {
        esp_app_desc_t run_app_info = {0};
        esp_ota_get_partition_description(run_app_partition, &run_app_info);
        uint16_t len = strlen(strchr(run_app_info.version, '.') + 1);
        if (len == 1)
            len = 10;
        else if (len == 2)
            len = 100;
        else if (len == 3)
            len = 1000;
        uint16_t version = atof(run_app_info.version) * len;
        ota->version = version / len << 8 | version % len;
#ifdef CONFIG_LIBS_USE_DBUS
        dbus_change_to_process("ota_version", ota->version);
        dbus_change_to_process("app_version", ota->version);
#endif
    }

    nvs_handle_t handle;
    if (nvs_open("ota", NVS_READWRITE, &handle) == ESP_OK)
    {
        uint16_t version = 0;
        ESP_LOGI(TAG, "Current version : %d.%d", ota->version >> 8, ota->version & 0xFF);
        if (nvs_get_u16(handle, "version", &version) == ESP_OK)
        {
            ESP_LOGI(TAG, "Last version : %d.%d", version >> 8, version & 0xFF);
            // if (ota->version > version) /* 升级成功 */
            //     dbus_only_change("ota_update", 1);
            // else if (ota->version < version) /* 升级失败 */
            //     dbus_only_change("ota_update", 2);
        }

        if (ota->version != version)
        {
            if (nvs_set_u16(handle, "version", ota->version) == ESP_OK)
                nvs_commit(handle);
            else
                ESP_LOGE(TAG, "Failed to save ota version to nvs.");
        }
        nvs_close(handle);
    }

    /* 开机检测按键是否按下, 如果是则检查路径中, 是否存在可升级文件 */
    // uint8_t level = hal_gpio_get_level(0);
    // if (!(level & 0x1))
    //     sdcard_mount_cb("/sdcard");

    do
    {
        ota_overflow_check(ota);
        /* 服务目录检测OTA升级任务 */
        // uint8_t type_list[] = {
        //     OTA_TYPE_WWW,
        //     OTA_TYPE_APP};
        // if (ota->cmd_run)
        // {
        //     for (uint8_t i = 0; i < 5; i++)
        //     {
        //         if (strlen(ota->cmd_list[i]))
        //         {
        //             if (type_list[i] == OTA_TYPE_OTHER)
        //                 strncpy(ota->par_name, "otadata", sizeof(ota->par_name));
        //             ota_set_url(ota, ota->cmd_list[i], type_list[i]);
        //             memset(ota->cmd_list[i], 0, 256);
        //             break;
        //         }
        //     }
        //     if (!ota->run)
        //     {
        //         ota->cmd_run = 0;
        //     }
        // }
        if (ota->run == 1)
        {
            // hal_global->hal.ota.states = 1;
            ota->status = OTA_STATUS_NULL;
            ota->progress = 0;
            ota->write_size = 0;
            ota->read_size = 0;
            ota->par_info = 0;
            ota->total_time = esp_log_timestamp();
            ota_http_update_url(ota->type, ota->url);

            ota->total_time = esp_log_timestamp() - ota->total_time;
            ESP_LOGI(TAG, "total time : %ld.%lds", ota->total_time / 1000, (ota->total_time % 1000) / 100);
            ota->run = 0;
            // hal_global->hal.ota.states = 0;
        }
        else if (ota->plugins) /* 执行其它 OTA 插件任务 */
        {
            /* 执行 ota_admin 插件任务 */
            ota_plugin_t plugin = ota->plugins;
            while (plugin)
            {
                if (plugin->loop)
                    plugin->loop(ota, plugin->user_data);
                plugin = plugin->next;
            }
        }
        if (ota->tick && (esp_log_timestamp() > ota->tick))
        {
            ESP_LOGI(TAG, "restart device.\n");
            // esp_system_abort("OTA Restart");
            esp_restart();
        }
        vTaskDelay(pdMS_TO_TICKS(10));
    } while (!ota->abort);
    ota->handle = 0;
}

void ota_init(void)
{
    ota_default_handle = _malloc_(sizeof(ota_s));
    if (!ota_default_handle)
    {
        ESP_LOGE(TAG, "Failed to malloc ota object.");
        return;
    }
    memset(ota_default_handle, 0, sizeof(ota_s));

    xTaskCreatePinnedToCore(ota_task_handler, "ota", 5 * 1024, ota_default_handle, 9, &ota_default_handle->handle, portNUM_PROCESSORS - 1);
    // xTaskCreatePinnedToCore(ota_task_handler, "ota", 3072, ota_default_handle, configMAX_PRIORITIES / 2 + 3, &ota_default_handle->handle, 1);

    if (!ota_default_handle->handle)
        ESP_LOGE(TAG, "Failed to create ota task.");
}
// #include "hw_init.h"
// INIT_ENV_EXPORT(ota_auto_init);
// #include "api_map.h"
// ADD_API(ota_auto_init)
